/*
 * @(#) GameModel.java
 *
 * Copyright 2006 - 2009 by the original authors of Galaxy
 * and all its contributors ("Galaxy.org").
 *
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package kyodai;

/**
 * Game model.
 *
 * @author Cheng Liang
 * @version 1.0
 */
public class GameModel {

    /** Game level number. */
    public int level = Resource.NORMAL;
    
    /** Count of hints. */
    public int hintCnt = 3;
    
    /** Count of reset. */
    public int resetCnt = 3;
    
    /** Section number. */
    public int section = 1; // FIXME set to 1
    
    /** Current time. */
    public int currTime;
    
    /** Map. */
    public int[][] map = null;
    
    /** Row number. */
    public int row = 8;
    
    /** Column number. */
    public int column = 8;
    
    /** Rest checker number. */
    public int restCheckers;
    
    /** Total number of checkers. */
    private int total;
    
    /** Resource handler. */
    private Resource res = Resource.getInstance();

    /**
     * Game start, initialize game map.
     */
    private void init() {
        int checkerCnt = 16;
        switch (level) {
            case 0: // easy
                checkerCnt = 9;
                row = 6;
                column = 6;
                break;
            case 1: // normal
                checkerCnt = 16;
                row = 8;
                column = 8;
                break;
            case 2: // hard, maximum is (ROW * COLUMN / 4)
                checkerCnt = 25;
                row = 10;
                column = 10;
                break;
        }
        total = row * column;
        int[] tempMap = new int[total];
        restCheckers = checkerCnt * 4;
        for (int i = 0; i < checkerCnt; i++) {
            tempMap[i * 4] = i + 1;
            tempMap[i * 4 + 1] = i + 1;
            tempMap[i * 4 + 2] = i + 1;
            tempMap[i * 4 + 3] = i + 1;
        }
        for (int i = tempMap.length; i > 0; i--) {
            int j = res.nextInt(i);
            int tmp = tempMap[j];
            tempMap[j] = tempMap[i - 1];
            tempMap[i - 1] = tmp;
        }
        map = new int[row][column];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                map[i][j] = tempMap[i * column + j];
            }
        }
        tempMap = null;
    }

    /**
     * Gets next section number.
     */
    public void nextSection() {
        section++;
        currTime = 0;
        init();
    }

    /**
     * start game.
     *
     * @param restart true, if this is restart game
     */
    public void start(boolean restart) {
    	if(restart) {
    		hintCnt = 3;
    		resetCnt = 3;
    		currTime = 0;
    	}        
        init();
    }

    /**
     * Finds hint points.
     *
     * @param lastPoint the last point
     *
     * @return two matches points
     */
    public Point[] hint(Point lastPoint) {
        if (hintCnt > 0) {
            hintCnt--;
            return findMatch(lastPoint);
        }
        if (hintCnt < 0) {
            hintCnt = 0;
        }
        return null;
    }

    /**
     * Reset game map.
     *
     * @return true, if successful
     */
    public boolean reset() {
        if (resetCnt == 0 || restCheckers == 0) {
            return false;
        }
        resetCnt--;
        int[] tmp = new int[total];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                tmp[i * column + j] = map[i][j];
            }
        }
        for (int i = tmp.length; i > 0; i--) {
            int j = res.nextInt(i);
            int t = tmp[i - 1];
            tmp[i - 1] = tmp[j];
            tmp[j] = t;
        }
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                map[i][j] = tmp[i * column + j];
            }
        }
        tmp = null;
        switch (section) {
            case 1: // non-move
                break;
            case 2: // to top
            	for (int i = 0; i < column; i++) {
                    toTop(i);
                }
                break;
            case 3: // to bottom
            	for (int i = 0; i < column; i++) {
                    toBottom(i);
                }
                break;
            case 4: // to left
            	for (int i = 0; i < row; i++) {
                    toLeft(i);
                }
                break;
            case 5: // to right
            	for (int i = 0; i < row; i++) {
                    toRight(i);
                }
                break;
            case 6: // to side horizontally
                //toSideHorizontally(a, b);
                break;
            case 7: // to side vertically
//			toSideVertically(a, b);
                break;
        }
        return true;
    }

    /**
     * Checks for two points on game map.
     *
     * @param a Point A
     * @param b Point B
     * @return <code>true</code> if match
     */
    public boolean checkMatch(Point a, Point b) {
        if (map[a.x][a.y] != map[b.x][b.y]) {
            return false;
        }
        if ((a.x == b.x) && horizontalMatch(a, b, true)) {
            return true;
        }
        if ((a.y == b.y) && verticalMatch(a, b, true)) {
            return true;
        }
        if (oneCornerMatch(a, b)) {
            return true;
        }
        if (twoCornerMatch(a, b)) {
            return true;
        }
        return false;
    }

    /**
     * Erases two points which are match.
     *
     * @param a Point A
     * @param b Point B
     */
    public void erase(Point a, Point b) {
        map[a.x][a.y] = 0;
        map[b.x][b.y] = 0;
        restCheckers -= 2;
        switch (section) {
            case 1: // non-move
                break;
            case 2: // to top
                toTop(a.x);
                toTop(b.x);
                break;
            case 3: // to bottom
            	toBottom(a.x);
                toBottom(b.x);
                break;
            case 4: // to left
            	toLeft(a.y);
                toLeft(b.y);
                break;
            case 5: // to right
            	toRight(a.y);
                toRight(b.y);
                break;
            case 6: // to side horizontally
                toSideHorizontally(a, b);
                break;
            case 7: // to side vertically
                toSideVertically(a, b);
                break;
        }
    }

    /**
     * Each checker in one column move to the top of the grid.
     * 
     * @param col the index of this column
     */
    private void toTop(int col) {
        int[] tmp = new int[column];
        for (int i = 0, ai = 0; i < tmp.length; i++) {
            tmp[i] = 0;
            if (map[col][i] != 0) {
                tmp[ai++] = map[col][i];
            }
        }
        for (int i = 0; i < column; i++) {
            map[col][i] = tmp[i];
        }
        tmp = null;
    }

    /**
     * Each checker in one column move to the bottom of the grid.
     * 
     * @param col the index of this column
     */
    private void toBottom(int col) {
        int[] tmp = new int[column];
        for (int i = column - 1, ai = column - 1; i >= 0; i--) {
            tmp[i] = 0;
            if (map[col][i] != 0) {
                tmp[ai--] = map[col][i];
            }
        }
        for (int i = 0; i < column; i++) {
            map[col][i] = tmp[i];
        }
        tmp = null;
    }

    /**
     * Each checker in one row move to the left of the grid.
     * 
     * @param col the index of this row
     */
    private void toLeft(int r) {
        int[] tmp = new int[row];
        for (int i = 0, ai = 0; i < row; i++) {
            tmp[i] = 0;
            if (map[i][r] != 0) {
                tmp[ai++] = map[i][r];
            }
        }
        for (int i = 0; i < row; i++) {
            map[i][r] = tmp[i];
        }
        tmp = null;
    }

    /**
     * Each checker in one row move to the right of the grid.
     * 
     * @param col the index of this row
     */
    private void toRight(int r) {
        int[] tmp = new int[row];
        for (int i = row - 1, ai = row - 1; i >= 0; i--) {
            tmp[i] = 0;
            if (map[i][r] != 0) {
                tmp[ai--] = map[i][r];
            }
        }
        for (int i = 0; i < row; i++) {
            map[i][r] = tmp[i];
        }
        tmp = null;
    }

    // TODO incorrect algorithm
    private void toSideHorizontally(Point a, Point b) {
        int middle = row >> 1;
        if (a.x < middle) {
            // to top
            for (int i = a.x; i < middle - 1; i++) {
                int temp = map[i][a.y];
                map[i][a.y] = map[i + 1][a.y];
                map[i + 1][a.y] = temp;
            }
        } else {
            for (int i = a.x; i > middle; i--) {
                int temp = map[i][a.y];
                map[i][a.y] = map[i - 1][a.y];
                map[i - 1][a.y] = temp;
            }
        }
        if (b.x < middle) {
            // to top
            for (int i = b.x; i < middle - 1; i++) {
                int temp = map[i][b.y];
                map[i][b.y] = map[i + 1][b.y];
                map[i + 1][b.y] = temp;
            }
        } else {
            for (int i = b.x; i > middle; i--) {
                int temp = map[i][b.y];
                map[i][b.y] = map[i - 1][b.y];
                map[i - 1][b.y] = temp;
            }
        }
    }

    // TODO incorrect algorithm
    private void toSideVertically(Point a, Point b) {
        int middle = column / 2;
        if (a.y < middle) {
            // to left
            for (int i = a.y; i < middle - 1; i++) {
                int temp = map[a.x][i];
                map[a.x][i] = map[a.x][i + 1];
                map[a.x][i + 1] = temp;
            }
        } else {
            for (int i = a.y; i > middle; i--) {
                int temp = map[a.x][i];
                map[a.x][i] = map[a.x][i - 1];
                map[a.x][i - 1] = temp;
            }
        }
        if (b.y < middle) {
            // to left
            for (int i = b.y; i < middle - 1; i++) {
                int temp = map[b.x][i];
                map[b.x][i] = map[b.x][i + 1];
                map[b.x][i + 1] = temp;
            }
        } else {
            for (int i = b.y; i > middle; i--) {
                int temp = map[b.x][i];
                map[b.x][i] = map[b.x][i - 1];
                map[b.x][i - 1] = temp;
            }
        }
    }

    /**
     * Finds match points to hint.
     *
     * @param a Point A
     * @return two points that are matched
     */
    public Point[] findMatch(Point a) {
        Point b = new Point(0, 0);
        a = findFirst(a);
        if (a == null) {
            return null;
        }
        while (a != null) {
            b = findSecond(a, b);
            while (b != null) {
                if (checkMatch(a, b)) {
                    return new Point[]{a, b};
                }
                b = findSecond(a, b);
            }
            a = findFirst(a);
            b = null;
        }
        return null;
    }

    private Point findFirst(Point a) {
        int offset = 0;
        if (a != null) {
            offset = a.x * column + a.y;
        }
        if (offset < 0) {
            offset = -1;
        }
        for (int i = offset + 1; i < total; i++) {
            int row = i / column;
            int col = i - row * column;
            if (map[row][col] != 0) {
                return new Point(row, col);
            }
        }
        return null;
    }

    private Point findSecond(Point a, Point b) {
        if (a == null) {
            return null;
        }
        if (b == null) {
            b = new Point(0, 0);
        } else {
            if (a.x + b.x < 0) {
                return null;
            }
        }
        int offsetA = a.x * column + a.y;
        int offsetB = b.x * column + b.y;
        int offset = offsetA < offsetB ? offsetB : offsetA;
        for (int i = offset + 1; i < total; i++) {
            int row = i / column;
            int col = i - row * column;
            if (map[row][col] == map[a.x][a.y]) {
                return new Point(row, col);
            }
        }
        return null;
    }

    private boolean horizontalMatch(Point a, Point b, boolean animateDelete) {
        if (a.x == b.x && a.y == b.y) {
            return false;
        }
        int yStart = a.y <= b.y ? a.y : b.y;
        int yEnd = a.y <= b.y ? b.y : a.y;
        for (int y = yStart + 1; y < yEnd; y++) {
            if (map[a.x][y] != 0) {
                return false;
            }
        }
        return true;
    }

    private boolean verticalMatch(Point a, Point b, boolean animateDelete) {
        if (a.x == b.x && a.y == b.y) {
            return false;
        }
        int xStart = a.x <= b.x ? a.x : b.x;
        int xEnd = a.x <= b.x ? b.x : a.x;
        for (int x = xStart + 1; x < xEnd; x++) {
            if (map[x][a.y] != 0) {
                return false;
            }
        }
        return true;
    }

    private boolean oneCornerMatch(Point a, Point b) {
        Point c = new Point(a.x, b.y);
        Point d = new Point(b.x, a.y);
        if (map[c.x][c.y] == 0) {
            if (verticalMatch(b, c, false) && horizontalMatch(a, c, false)) {
                c = null;
                d = null;
                return true;
            }
        }
        if (map[d.x][d.y] == 0) {
            if (verticalMatch(a, d, false) && horizontalMatch(b, d, false)) {
                c = null;
                d = null;
                return true;
            }
        }
        c = null;
        d = null;
        return false;
    }

    private boolean twoCornerMatch(Point a, Point b) {
        for (int y = 0; y < column; y++) {
            Point p1 = new Point(a.x, y);
            Point p2 = new Point(b.x, y);
            if (map[a.x][y] == 0 && map[b.x][y] == 0 
            		&& verticalMatch(p1, p2, false)) {
                if (horizontalMatch(p1, a, false) 
                		&& horizontalMatch(p2, b, false)) {
                    p1 = null;
                    p2 = null;
                    return true;
                }
            }
            p1 = null;
            p2 = null;
        }
        for (int x = 0; x < row; x++) {
            Point p1 = new Point(x, a.y);
            Point p2 = new Point(x, b.y);
            if (map[x][a.y] == 0 && map[x][b.y] == 0 
            		&& horizontalMatch(p1, p2, false)) {
                if (verticalMatch(p1, a, false) 
                		&& verticalMatch(p2, b, false)) {
                    p1 = null;
                    p2 = null;
                    return true;
                }
            }
            p1 = null;
            p2 = null;
        }
        return false;
    }
}
